/*
 *
 * ROK-104001 flashing application
 *
 * Author: Craig Hughes <craig@gumstix.com>
 * Latest version available from: $HeadURL: http://svn.gumstix.com/gumstix-buildroot/trunk/package/rok-flash/src/rok-flash.c $
 * This version: $Id: rok-flash.c 865 2006-02-28 23:56:36Z craig $
 *
 * Please send patches to Craig at the above email address
 *
 * Copyright (c) 2005, Gumstix, Inc. All rights reserved.
 *
 * 	Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 	- Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * - Neither the name of Gumstix, Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * 	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <getopt.h>
#include <errno.h>

#include "mfl-header.h"

#include "rok-flash.h"

static char uartboot_name[1024], flashboot_name[1024], application_name[1024], tty_name[1024];
static char DEBUG=0;
int uart = -1;

void uart_open(void)
{
	uart = open(tty_name,O_RDWR);
	if(-1 == uart)
	{
		perror("Failed to open UART");
		exit(1);
	}

// Set up the terminal info
	struct termios my_termios;
	tcgetattr(uart, &my_termios);

	// Initalize to 9600
	cfmakeraw(&my_termios);
	cfsetispeed(&my_termios, B9600);
	cfsetospeed(&my_termios, B9600);
	my_termios.c_iflag &= ~(IXON|IXOFF);
	my_termios.c_cflag &= ~(CRTSCTS);
	my_termios.c_cflag |= CLOCAL;
	my_termios.c_lflag &= ~(ICANON|ISIG);

	if(-1 == tcsetattr(uart, TCSAFLUSH, &my_termios))
	{
		perror("Failed to set termios on the UART");
		exit(1);
	}

	if(DEBUG) printf("Opened UART on '%s'\n", tty_name);
}

void uart_close(void)
{
	close(uart);
	uart = -1;
}

speed_t uart_get_speed()
{
	struct termios my_termios;
	tcgetattr(uart, &my_termios);
	return cfgetospeed(&my_termios);
}

void uart_set_speed(const speed_t new_speed)
{
	struct termios my_termios;
	tcgetattr(uart, &my_termios);
	cfsetospeed(&my_termios, new_speed);
	cfsetispeed(&my_termios, new_speed);
	if(-1 == tcsetattr(uart, TCSAFLUSH, &my_termios))
	{
		perror("Failed to set speed on the UART");
		exit(1);
	}
	if(DEBUG) printf("Set UART speed to o%08o\n",new_speed);
}

void uart_flush()
{
	if(-1 == tcflush(uart, TCIOFLUSH))
	{
		perror("UART flush failed");
		exit(1);
	}
}

void uart_write(const unsigned char *buf, size_t len)
{
	if(DEBUG) printf("Writing len %d:");
	ssize_t written = 0;
	for(written=0; written<len && written<16; written++)
	{
		if(DEBUG) printf(" %02x",buf[written]);
	}
	if(DEBUG) printf(written<len ? "...\n" : "\n");
	while(len > 0)
	{
		written = write(uart, buf, len);
		if(-1 == written)
		{
			perror("Write to uart failed");
			if(DEBUG) printf("uart=%d, buf=%p, len=%d\n",uart, buf, len);
			exit(1);
		}
		len -= written;
		buf += written;
	}
}

void uart_write_byte(const unsigned char byte)
{
	uart_write(&byte, 1);
}

int uart_read_byte(void)
{
	ssize_t read_bytes = 0;
	unsigned char ret;
	fd_set rd_set;
	struct timeval tv;
	
	FD_ZERO(&rd_set);
	FD_SET(uart, &rd_set);
	tv.tv_sec = 5; // 5 second timeout on reads
	tv.tv_usec = 0;
	int result = select(uart+1, &rd_set, NULL, NULL, &tv);
	if(-1 == result)
	{
		perror("Select failed reading from UART");
		exit(1);
	}
	else if(0 == result)
	{
		if(DEBUG) printf("Select timed out\n");
		return -1;
	}

	read_bytes = read(uart, &ret, 1);
	if(-1 == read_bytes)
	{
		perror("Read from uart failed");
		return -2;
	}

	if(DEBUG) printf("Read byte 0x%02x\n", ret);
	return ret;
}

int uart_check_byte(unsigned char c)
{
	int resp = uart_read_byte();
	
	if(-2 == resp) { printf("Read failed while looking for '%c' (0x%02x)\n", c, c); return resp; }
	if(-1 == resp) { printf("Timeout while looking for '%c' (0x%02x)\n", c, c); return -1; }
	if(c != (unsigned char)resp) { printf("Expecting '%c' (0x%02x), but received '%c' (0x%02x)\n", c, c, (unsigned char)resp, (unsigned char)resp); return -2; }
	return 0;
}

void uart_wait_for_break(void)
{
	while(uart_check_byte(0)) continue;
}

void read_fully(int fd, unsigned char *buf, size_t len)
{
	ssize_t res = 0;
	while(len > 0)
	{
		res = read(fd, buf, len);
		if(-1 == res)
		{
			perror("Failed to read from file");
			exit(1);
		}
		len -= res;
	}
}

void reset_bt_module(void)
{
	FILE *bt_reset_gpio = fopen("/proc/gpio/GPIO7","w");
	fprintf(bt_reset_gpio,"clear");
	fclose(bt_reset_gpio);
	usleep(1000); // one millisecond
	bt_reset_gpio = fopen("/proc/gpio/GPIO7","w");
	fprintf(bt_reset_gpio,"set");
	fclose(bt_reset_gpio);
}

void usage(void)
{
	printf("--uartboot=filename | -u filename\t}\n");
	printf("--flashboot=filename | -f filename\t} Set the filename for the particular flash component\n");
	printf("--application=filename | -a filename\t}\n");
	printf("--tty=/path/to/tty | -t /path/to/tty\n");
	printf("--verbose | -v\t\t\t\tTurn on verbose mode\n");
	printf("--help | -h\t\t\t\tPrint this help message\n");
}

void parse_args(int argc, char *argv[])
{
	strncpy(uartboot_name, UARTBOOT_PATH, 1024);
	strncpy(flashboot_name, FLASHBOOT_PATH, 1024);
	strncpy(application_name, APPLICATION_PATH, 1024);
	strncpy(tty_name, TTY_PATH, 1024);

	while(1)
	{
		int this_option_optind = optind ? optind : 1;
		int option_index = 0;
		static struct option long_options[] = {
			{ "uartboot", 1, 0, 'u' },
			{ "flashboot", 1, 0, 'f' },
			{ "application", 1, 0, 'a' },
			{ "tty", 1, 0, 't' },
			{ "verbose", 1, 0, 'v' },
			{ "help", 0, 0, 'h' },
			{ 0,0,0,0 }
		};

		int c = getopt_long(argc, argv, "u:f:a:t:vh", long_options, &option_index);
		if(c == -1) break;

		switch(c)
		{
			case 0:
				printf("Unknown option: '%s'", long_options[option_index].name);
				if(optarg) printf(" with arg '%s'", optarg);
				printf("\n");
			case 'h':
				usage();
				exit(c==0);
			case 'v':
				DEBUG=1;
				break;
			case 'u':
				strncpy(uartboot_name, optarg, 1024);
				break;
			case 'f':
				strncpy(flashboot_name, optarg, 1024);
				break;
			case 'a':
				strncpy(application_name, optarg, 1024);
				break;
			case 't':
				strncpy(tty_name, optarg, 1024);
				break;
			default:
				printf("?? getopt returned character code 0x%x ??\n",c);
				usage(); exit(1);
		}
	}

	if(optind < argc)
	{
		printf("non-option ARGV elements found: ");
		while(optind < argc)
		{
			printf("'%s' ", argv[optind++]);
		}
		printf("\n"); usage(); exit(1);
	}
}

int program_flash_image(const char *filename)
{
	uart_write_byte('P');
	if(uart_check_byte('!'))
	{
		printf("Failed to read '!' after sending 'P' command\n");
		return 1;
	}

	if(DEBUG) printf("Programming flash...\n");

	int flash_file = open(filename, O_RDONLY);
	if(-1 == flash_file)
	{
		perror("Error opening flash_file");
		exit(1);
	}

	struct stat flash_stat;
	if(-1 == fstat(flash_file, &flash_stat))
	{
		perror("Failed to stat flash_file");
		exit(1);
	}

	unsigned char *mflBuf = malloc(flash_stat.st_size);
	read_fully(flash_file, mflBuf, flash_stat.st_size);

	MflHeader mflHdr;
	memcpy(&mflHdr, mflBuf, MFLHDR);
	if(DEBUG) printf("%3s v.:0x%02x c.t.: 0x%02x img: %d\n", mflHdr.MflSign, mflHdr.MflVers, mflHdr.ChipType, mflHdr.NoOfImages);
	unsigned char *imgBuf = mflBuf + MFLHDR;

	int i;
	for(i=0; i < mflHdr.NoOfImages; i++)
	{
		ImageHeader ImgHdr;
		memcpy(&ImgHdr, imgBuf, IMAGEHDR);
	if(DEBUG) printf("IMG: type 0x%02x, vers 0x%02x, crc 0x%02x, "
			"size 0x%08x, offset 0x%08x, addr 0x%08x, name \"%9s\"\n",
			ImgHdr.ImageType, ImgHdr.SectionVersion, ImgHdr.CrcWord,
			ImgHdr.SegmentSize, ImgHdr.SegmentOffset, ImgHdr.SmcAddr,
			ImgHdr.SegmentName);
		imgBuf += IMAGEHDR;

		uart_write((unsigned char *)&(ImgHdr.SmcAddr), 4);

		uart_write((unsigned char *)&(ImgHdr.SegmentSize), 4);

		unsigned char res = uart_read_byte();
		if(res == '.')
		{
			// Error if we didn't get ':'
			uart_write_byte('E');
			unsigned char error[2];
			error[0] = uart_read_byte();
			error[1] = uart_read_byte();
			uart_read_byte(); // Suck up the '.'
			printf("Got error: '%2s' after sending flash size\n", error);
			return 1;
		}
		else if(res != ':')
		{
			printf("Didn't get ':' after sending size, got 0x%02x instead\n", res);
			return 1;
		}

		if(DEBUG) printf("Waiting now for flash to be erased...\n");
		res = uart_read_byte();
		if(res == '.')
		{
			// Error if we didn't get ','
			uart_write_byte('E');
			unsigned char error[2];
			error[0] = uart_read_byte();
			error[1] = uart_read_byte();
			uart_read_byte(); // Suck up the '.'
			printf("Got error: '%2s' after sending flash size\n", error);
			return 1;
		}
		else if(res != ',')
		{
			printf("Didn't get ',' from erasing flash, got 0x%02x instead\n", res);
			return 1;
		}

		// Send the flash image
		printf("Sending flash image...\n");
		uart_write(mflBuf + ImgHdr.SegmentOffset, ImgHdr.SegmentSize);
		printf("wrote %d bytes\n", ImgHdr.SegmentSize);

		res = uart_read_byte();
		if(res != '.')
		{
			printf("Failed to read '.' after sending flash image, got 0x%02x instead\n", res);
			return 1;
		}

		res = uart_read_byte();
		if(res != '>')
		{
			printf("Didn't get new prompt after sending flash image, got 0x%02x instead\n", res);
			return 1;
		}

		printf("Checking CRC...\n");
		uart_write_byte('R');
		res = uart_read_byte();
		uart_read_byte(); // Suck the '.'
		uart_read_byte(); // Suck the '>'
		// Check CRC
		if(ImgHdr.CrcWord != res)
		{
			printf("Bad CRC: expecting %d got %d\n", ImgHdr.CrcWord, res);
			return 1;
		}
		printf("Good CRC\n");

	}

	free(mflBuf);
	return 0;
}

void quit_and_reset(void)
{
	uart_write_byte('Q');
	while(uart_read_byte() != '.') continue;
	while(uart_read_byte() != '>') continue;
}

int send_parameters(unsigned char *buf, size_t parameter_size)
{
	uart_flush();

	// Write a couple of 0's before sending the first non-zero parameter:
	uart_write_byte(0x00);
	uart_write_byte(0x00);
	uart_write_byte(0x00);
	uart_write_byte(0x00);
	uart_write_byte(0x00);
	uart_write_byte(0x00);

	printf("Sending parameter block...\n");
	uart_write(buf, parameter_size);

	unsigned char r[6];
	int i;
	for(i=0; i<5; i++) r[i] = uart_read_byte();

	if(r[0]=='R' && r[1]=='E' && r[2]=='A' && r[3]=='D' && r[4]=='Y')
	{
		printf("Parameters sent OK\n");
		return 0;
	}
	
	printf("Failed to read READY after sending uart params ('%5s')\n",r);
	return 1;
}

int send_uartboot_image(unsigned char *buf, size_t read_size)
{
	int retries;
	for(retries=0; retries<2; retries++)
	{
		// Write a couple of 0xff's before sending "ST":
		uart_write_byte(0xff);
		uart_write_byte(0xff);
		uart_write_byte(0xff);
		uart_write_byte(0xff);
		uart_write_byte('S');
		uart_write_byte('T');
	
		unsigned char r[5];
		int i;
		for(i=0; i<2; i++) r[i] = uart_read_byte();
		if(!(r[0] == 'G' && r[1] == 'O'))
		{
			printf("Expecting 'GO' after sending 'ST', got '%2s'\n",r);
			return 1;
		}
		
		printf("Writing uartboot image...\n");
	
		uart_write(buf, read_size);
		for(i=0; i<4; i++) r[i] = uart_read_byte();
	
		if(r[0]=='D' && r[1]=='O' && r[2]=='N' && r[3]=='E')
		{
			printf("Done\n");
			return 0;
		}
	}
	
	printf("Failed to receive 'DONE' after sending uartboot image\n");
	return 1;
}

int download_fl(void)
{
	unsigned char res = uart_read_byte();
	if(res != '>')
	{
		printf("Was expecting FL '>' prompt, but got 0x%02x instead\n",res);
		return 1;
	}

	printf("Switching baud rate...\n");
	uart_write("C5", 2); // C7 is 115200
	res = uart_read_byte();
/* The byte which comes back should be 0x21, but in fact it seems to be 0xa1, ie 0x21 plus high bit set
   could be that the baseband switched baudrates a little early...
	if(res != '!')
	{
		printf("Was expecting '!' after baudrate change command, got 0x%02x instead\n",res);
		return 1;
	}
	*/

	uart_set_speed(B115200);
	usleep(100); // Wait for baud rate change to kick in
	
	uart_write_byte('!');
	if(uart_check_byte('.'))
	{
		printf("Failed to read '.' after changing baudrate and sending '!'\n");
		return 1;
	}
	printf("Done with baud switch\n");

	if(uart_check_byte('>'))
	{
		printf("Failed to read '>' prompt after changing baudrate\n");
	}
	
	printf("Programming flash boot image...\n");
	if(program_flash_image(flashboot_name) != 0)
	{
		printf("Failed to load flash boot image\n");
		return 1;
	}

	printf("Done programming flash boot image\nProgramming application image...\n");

	if(program_flash_image(application_name) != 0)
	{
		printf("The device did not answer when code image was downloaded.\n");
		return 1;
	}
	printf("Done programming application image\n");
	
	// Application image successfully downloaded

	quit_and_reset();

	return 0;
}

int download_uartboot(void)
{
	int uartFile = open(uartboot_name, O_RDONLY);
	if(-1 == uartFile)
	{
		perror("Failed to open uartboot file");
		exit(1);
	}

	struct stat uartStat;
	if(-1 == fstat(uartFile, &uartStat))
	{
		perror("Failed to stat uartboot file");
		exit(1);
	}

	unsigned char *mflBuf = (unsigned char *)malloc(uartStat.st_size);
	read_fully(uartFile, mflBuf, uartStat.st_size);
	
	MflHeader mflHdr;
	memcpy(&mflHdr, mflBuf, MFLHDR);
	if(DEBUG) printf("%3s v.:0x%02x c.t.: 0x%02x img: %d\n", mflHdr.MflSign, mflHdr.MflVers, mflHdr.ChipType, mflHdr.NoOfImages);
	unsigned char *imgBuf = mflBuf + MFLHDR;

	if(DEBUG) printf("%3s v.:0x%02x c.t.: 0x%02x img: %d\n", mflHdr.MflSign, mflHdr.MflVers, mflHdr.ChipType, mflHdr.NoOfImages);
	ImageHeader imgHdr1;
	memcpy(&imgHdr1, imgBuf, IMAGEHDR);
	if(DEBUG) printf("IMG: type 0x%02x, vers 0x%02x, crc 0x%02x, "
			"size 0x%08x, offset 0x%08x, addr 0x%08x, name \"%9s\"\n",
			imgHdr1.ImageType, imgHdr1.SectionVersion, imgHdr1.CrcWord,
			imgHdr1.SegmentSize, imgHdr1.SegmentOffset, imgHdr1.SmcAddr,
			imgHdr1.SegmentName);
	ImageHeader imgHdr2;
	memcpy(&imgHdr2, imgBuf + IMAGEHDR, IMAGEHDR);
	if(DEBUG) printf("IMG: type 0x%02x, vers 0x%02x, crc 0x%02x, "
			"size 0x%08x, offset 0x%08x, addr 0x%08x, name \"%9s\"\n",
			imgHdr2.ImageType, imgHdr2.SectionVersion, imgHdr2.CrcWord,
			imgHdr2.SegmentSize, imgHdr2.SegmentOffset, imgHdr2.SmcAddr,
			imgHdr2.SegmentName);

	if(send_parameters(mflBuf + imgHdr1.SegmentOffset, imgHdr1.SegmentSize) != 0)
	{
		printf("Failed to send parameters.\n");
		free(mflBuf);
		return 1;
	}

	if(send_uartboot_image(mflBuf + imgHdr2.SegmentOffset, imgHdr2.SegmentSize) != 0)
	{
		printf("Send uartboot image failed.\n");
		free(mflBuf);
		return 1;
	}

	// RAM image downloaded successfully
	close(uartFile);
	free(mflBuf);

	return 0;
}

int delete_flash_signature(void)
{
	uart_flush();

	int retries, resp=0;
	for(retries = 0; retries < 5 && resp != 'R'; retries++)
	{
		uart_write_byte('U');
		uart_write_byte('U');
		uart_write_byte('U');
		uart_write_byte('U');
		
		resp = uart_read_byte();
	}

	if(resp == 'R')
	{
		uart_write_byte('S');
		uart_write_byte('S');
		uart_write_byte('S');
		uart_write_byte('S');
		uart_write_byte('S');
	}

	while(uart_read_byte() >= 0) continue;

	return resp == 'R';
}

int check_for_baseband(void)
{
	printf("Looking for baseband...");
	int retries;
	for(retries=0; retries<3; retries++)
	{
		int i;
		for(i=0; i<5; i++) uart_write_byte('U');

		usleep(4 * 1000); // 4 millis

		unsigned char *want = "OK\n";
		for(i=0; i<3; i++)
		{
			if(uart_check_byte(want[i])) goto retry_check_baseband;
		}

		// If we read all 3 characters OK...
		printf("Baseband said 'OK'\n");
		return 0;
	
retry_check_baseband:
		uart_flush();
	}

	printf("No response from baseband\n");
	return -1;
}

void hci_erase(void)
{
	// Try the HCI erase command to see if it works
	uart_flush();
	uart_set_speed(B57600);

	usleep(100); // wait for new baudrate

	unsigned char data[] = { 0x01,
							0x03, 0xFD, 0x04,
							0x16, 0x3B, 0xE1, 0x52 };

	uart_write(data, 8);
	
	usleep(20 * 1000); // 20 millis

	uart_set_speed(B9600);
	
	usleep(100); // wait for new baudrate
}

int download(void)
{
	hci_erase();

	int res, tries=0;
	do
	{
		res = check_for_baseband();
		uart_flush();
		tries++;
	} while(res != 0 && tries < 3);

	if(res != 0)
	{
		reset_bt_module();
		uart_wait_for_break();

		tries = 0;
		do
		{
			res = delete_flash_signature();
			uart_flush();
			tries++;
		} while(!res && tries < 3);

		if(res == 0)
		{
			printf("Baseband did not report OK when queried\n");
			return 1;
		}
		
		tries=0;
		do
		{
			res = check_for_baseband();
			uart_flush();
			tries++;
		} while(!res && tries < 3);
		
		if(res != 0)
		{
			printf("Failed to get answer from baseband after flash erased\n");
			return 1;
		}
	}

	if(download_uartboot() != 0)
	{
		printf("Baseband did not accept uart params\n");
		return 1;
	}

	if(download_fl() != 0)
	{
		printf("Baseband did not allow flash to be loaded\n");
		return 1;
	}
}

int main(int argc, char *argv[])
{
	// reboot the sucker before we do anything else
	printf("Resetting BT module...\n");
	reset_bt_module();
	printf("Sleeping to let it settle...\n");
	sleep(2);
	printf("Settled.\n");

	parse_args(argc, argv);

	uart_open();

	if(download() == 0)
	{
		printf("Success!!!\n");
		uart_close();
		return 0;
	}
	else
	{
		printf("Failed!!!\n");
		uart_close();
		return 1;
	}
}
